package client

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"longmen/server/config/global"
	"longmen/server/pkg/common/helper/const"
	"longmen/server/pkg/common/helper/encrypt"
	http2 "longmen/server/pkg/common/helper/http"
	"longmen/server/pkg/common/helper/util"
	"longmen/server/pkg/common/payment"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strings"
)

const (
	// 支付名稱
	jcfPayName = "JCFPAY"
)

func init() {
	err := payment.RegisterPay(jcfPayName, initJcfPayClient)
	if err != nil {
		global.Log.Error("msg", "register JCFPAY error", "err", err)
		return
	}
}

//1159的集成代付(出金)
type JcfPayClient struct {
	PayUrl string
	Mid    string
	Key    string
}

func initJcfPayClient(api payment.PayApi) (payment.PayClient, error) {
	if api.ReqUri == "" {
		return nil, errors.New("ReqUri is required")
	}
	if api.Mid == "" {
		return nil, errors.New("merchant id is empty")
	}
	if api.Key == "" {
		return nil, errors.New("Key is required")
	}

	return &JcfPayClient{
		PayUrl: api.ReqUri,
		Mid:    api.Mid,
		Key:    api.Key,
	}, nil
}

func (this *JcfPayClient) Sn() string {
	return ""
}

func (this *JcfPayClient) Pay(charge *payment.Charge) (*payment.PayInfo, error) {
	fName := "jcfPay.Pay"
	urlPath := this.PayUrl + "/payfor/req/order.pay"
	pData := ""
	headPara := make(map[string]string)
	response := ""
	signParam := ""
	var err error
	defer logPay(&fName, &urlPath, &pData, &signParam, headPara, &response, &err)

	info := &payment.PayInfo{}
	info.Type = "FORM" //form
	salt := this.getSalt(100000, 900000)
	req := &JcfPayReq{
		Salt:      salt,
		MOrder:    charge.OrderSn,
		Money:     this.Money2String(charge.Money),
		Notifyurl: charge.NotifyURL,
	}
	values := url.Values{}
	values.Add("salt", util.I2S(salt))
	values.Add("m_order", charge.OrderSn)
	values.Add("money", this.Money2String(charge.Money))
	values.Add("notify_url", charge.NotifyURL)
	sv := this.sortSignParameter(values)
	signParam = strings.Join(sv, "&") + "&key=" + this.Key
	sign := encrypt.Md5(signParam)
	req.Sign = sign
	headPara["mid"] = this.Mid
	headPara["Content-Type"] = "application/json"
	pData, _ = util.Struct2Json(req)
	result, err := http2.HttpHParaPost(urlPath, headPara, pData, _const.CONN_TIMEOUT_MS, _const.SERVE_TIMEOUT_MS)
	response = string(result)
	if err != nil {
		return info, err
	}

	resp := &JcfPayResp{}
	err = util.Json2Struct(string(result), resp)
	if err != nil {
		return info, err
	}
	if resp.Code != 200 {
		return nil, fmt.Errorf("%s error code %v(%s)", fName, resp.Code, resp.Msg)
	}

	payUrl := fmt.Sprintf("%s%s", this.PayUrl, resp.Data.Redirect)
	info.Content = this.toHTML(payUrl, charge.Mobile)
	info.ThirdSn = resp.Data.OrderNo
	return info, nil
}

func (this *JcfPayClient) toHTML(payURL string, isMobile bool) string {
	var blank = ""
	formatStr := `<form name="pay" action="%s" method="GET" %s>`
	if isMobile {
		blank = `target="_blank"`
		formatStr += `<button type='submit' class='v-btn v-btn--block v-btn--large v-btn--depressed v-btn--round theme--dark primary'>确认</button>`
	}
	formatStr += `</form>`

	return fmt.Sprintf(formatStr, payURL, blank)
}

func (this *JcfPayClient) VerifySign(req *http.Request) (*payment.BaseResult, error) {
	fName := "jcfPay.VerifySign"
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, fmt.Errorf("%s ReadAll %s", fName, err.Error())
	}
	fmt.Printf("%s body :%s\n", fName, string(body))

	r := &JcfWithdrawalSignReq{}
	err = util.Json2Struct(string(body), r)
	if err != nil {
		return nil, fmt.Errorf("%s body err Json2Struct %s", fName, err.Error())
	}

	info := &payment.BaseResult{}

	values := url.Values{}
	values.Add("m_order", r.MOrder)
	values.Add("orderno", r.Orderno)
	values.Add("money", r.Money)
	values.Add("status", util.I2S(r.Status))
	values.Add("resptime", r.Resptime)
	sv := this.sortSignParameter(values)
	signParam := strings.Join(sv, "&") + "&key=" + this.Key
	sign := encrypt.Md5(signParam)
	fmt.Printf("%s before sign: %s, my sign :%s , return sign %s\n", fName, signParam, sign, r.Sign)
	if r.Sign != sign {
		return info, errors.New("verify sign fail")
	}

	switch r.Status {
	case 1:
		info.Status = 2 //Processing
	case 3:
		info.Status = 1 //Success
	case 2, 4:
		info.Status = 2 //Failed
	default:
		info.Status = 2 //Failed
	}
	info.IsSucceed = true
	jsonVal, _ := json.Marshal(r)
	info.OrderSn = r.MOrder
	info.Params = string(jsonVal)
	return info, nil
}

func (this *JcfPayClient) Notify(br *payment.BaseResult) *payment.BaseNotify {
	notify := &payment.BaseNotify{}
	notify.Type = payment.STRING
	if br.IsSucceed {
		notify.Ret = "SUCCESS"
	}
	return notify
}

func (this *JcfPayClient) Withdrawal(charge *payment.Charge, w *payment.Withdrawal) (*payment.BaseResult, error) {
	fName := "jcfPay.Withdrawal"
	urlPath := this.PayUrl + "/req/order.settle"
	pData := ""
	headPara := make(map[string]string)
	response := ""
	signParam := ""
	var err error
	defer logPay(&fName, &urlPath, &pData, &signParam, headPara, &response, &err)

	info := &payment.BaseResult{}

	salt := this.getSalt(100000, 900000)
	req := &JcfWithdrawalReq{
		Salt:        salt,
		MOrder:      charge.OrderSn,
		Money:       this.Money2String(charge.Money),
		Accountno:   w.Card,
		Accountname: w.Name,
		Bankno:      w.BankCode,
		Notifyurl:   charge.NotifyURL,
	}
	values := url.Values{}
	values.Add("salt", util.I2S(salt))
	values.Add("m_order", charge.OrderSn)
	values.Add("money", this.Money2String(charge.Money))
	values.Add("accountno", w.Card)
	values.Add("accountname", w.Name)
	values.Add("bankno", w.BankCode)
	values.Add("notify_url", charge.NotifyURL)
	sv := this.sortSignParameter(values)
	signParam = strings.Join(sv, "&") + "&key=" + this.Key
	sign := encrypt.Md5(signParam)
	req.Sign = sign
	headPara["mid"] = this.Mid
	headPara["Content-Type"] = "application/json"
	pData, _ = util.Struct2Json(req)
	result, err := http2.HttpHParaPost(urlPath, headPara, pData, _const.CONN_TIMEOUT_MS, _const.SERVE_TIMEOUT_MS)
	response = string(result)
	if err != nil {
		return info, err
	}

	resp := &JcfWithdrawalResp{}
	err = util.Json2Struct(string(result), resp)
	if err != nil {
		return info, err
	}
	if resp.Code == 200 {
		info.IsSucceed = true
		return info, nil
	}
	return info, fmt.Errorf("%s error code %v(%s)", fName, resp.Code, resp.Msg)
}

func (this *JcfPayClient) WithdrawalSign(req *http.Request) (*payment.BaseResult, error) {
	fName := "jcfPay.WithdrawalSign"
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, fmt.Errorf("%s ReadAll %s", fName, err.Error())
	}
	fmt.Printf("%s body :%s\n", fName, string(body))

	r := &JcfWithdrawalSignReq{}
	err = util.Json2Struct(string(body), r)
	if err != nil {
		return nil, fmt.Errorf("%s body err Json2Struct %s", fName, err.Error())
	}

	info := &payment.BaseResult{}

	values := url.Values{}
	values.Add("m_order", r.MOrder)
	values.Add("orderno", r.Orderno)
	values.Add("money", r.Money)
	values.Add("status", util.I2S(r.Status))
	values.Add("resptime", r.Resptime)
	sv := this.sortSignParameter(values)
	signParam := strings.Join(sv, "&") + "&key=" + this.Key
	sign := encrypt.Md5(signParam)
	fmt.Printf("%s before sign: %s, my sign :%s , return sign %s\n", fName, signParam, sign, r.Sign)
	if r.Sign != sign {
		return info, errors.New("verify sign fail")
	}

	switch r.Status {
	case 1:
		info.Status = 2 //Processing
	case 3:
		info.Status = 1 //Success
	case 2, 4:
		info.Status = 2 //Failed
	default:
		info.Status = 2 //Failed
	}
	info.IsSucceed = true
	jsonVal, _ := json.Marshal(r)
	info.OrderSn = r.MOrder
	info.Params = string(jsonVal)
	return info, nil
}

func (this *JcfPayClient) getSalt(min, max int) int {
	return rand.Intn(max-min) + min
}

func (this *JcfPayClient) sortSignParameter(uv url.Values) []string {
	var params []string
	for k := range uv {
		params = append(params, k)
	}

	sort.Strings(params)
	for i, v := range params {
		params[i] = v + "=" + uv.Get(v)
	}

	return params
}

func (this *JcfPayClient) Money2String(money float64) string {
	return fmt.Sprintf("%.2f", money)
}

type JcfPayReq struct {
	Salt      int    `json:"salt"`       //随机数 用户加密的随机数int
	MOrder    string `json:"m_order"`    //交易序号 商家交易序号(自行产生具有唯一性)
	Money     string `json:"money"`      //支付金额 "以元为单位，格式为00.00，小数两位。  例:比方10元，提交金额时金额应为10.00 "
	Notifyurl string `json:"notify_url"` //商家接收出金结果网址 接收异步交易结果
	Sign      string `json:"sign"`       //验证码 详见签名规则章节
}

type JcfPayResp struct {
	Code int    `json:"code"` //状态代号 "200: 结算请求成功 400: 结算请求失败"
	Msg  string `json:"msg"`  //请求结果讯息
	Data struct {
		Redirect string `json:"redirect"` //支付路徑
		OrderNo  string `json:"order_no"` //訂單號
		Sign     string `json:"sign"`     //驗證碼
	} `json:"data"`
}

type JcfWithdrawalReq struct {
	Salt        int    `json:"salt"`        //随机数 用户加密的随机数int
	MOrder      string `json:"m_order"`     //交易序号 商家交易序号(自行产生具有唯一性)
	Money       string `json:"money"`       //支付金额 "以元为单位，格式为00.00，小数两位。  例:比方10元，提交金额时金额应为10.00 "
	Accountno   string `json:"accountno"`   //银行账号 e.g. 1111222244443333
	Accountname string `json:"accountname"` //持卡人姓名 e.g. 张添发
	Bankno      string `json:"bankno"`      //银行编号 "e.g. CCB,ABC,ICBC 具体参考附录"
	Notifyurl   string `json:"notify_url"`  //商家接收出金结果网址 接收异步交易结果
	Sign        string `json:"sign"`        //验证码 详见签名规则章节
}

type JcfWithdrawalResp struct {
	Code int    `json:"code"` //状态代号 "200: 结算请求成功 400: 结算请求失败"
	Msg  string `json:"msg"`  //请求结果讯息
}

type JcfWithdrawalSignReq struct {
	MOrder  string `json:"m_order"` //交易序号 商家交易序号
	Orderno string `json:"orderno"` //本系统交易序号 "本系统交易序号(具唯一性) 处理成功则回传，未成功则为空"
	Money   string `json:"money"`   //结算金额 "以元为单位，格式为00.00，小数两位。 例:比方10元，提交金额时金额应为10.00"
	Status  int    `json:"status"`  //结算结果
	/*
	   0:系统内部错误
	   1: pending
	   2: 渠道处理失败,订单关闭
	   3: 交易成功
	   4: 渠道拒绝此交易,订单关闭
	   交易结果以此字段的状态为主。
	*/
	Resptime string `json:"resptime"` //出金完成时间 处理成功则回传，未成功则为空
	Sign     string `json:"sign"`     //验证码 详见签名规则章节
}
